package plp.orientadaObjetos2.expressao.unaria;

import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ClasseNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoNaoDeclaradoException;
import plp.orientadaObjetos1.expressao.Expressao;
import plp.orientadaObjetos1.expressao.unaria.ExpUnaria;
import plp.orientadaObjetos1.expressao.valor.Valor;
import plp.orientadaObjetos1.expressao.valor.ValorRef;
import plp.orientadaObjetos1.memoria.AmbienteCompilacaoOO1;
import plp.orientadaObjetos1.memoria.AmbienteExecucaoOO1;
import plp.orientadaObjetos1.memoria.Objeto;
import plp.orientadaObjetos1.util.Tipo;
import plp.orientadaObjetos1.util.TipoClasse;
import plp.orientadaObjetos2.memoria.AmbienteCompilacaoOO2;
import plp.orientadaObjetos2.memoria.AmbienteExecucaoOO2;
import plp.orientadaObjetos2.memoria.DefClasseOO2;
import static plp.orientadaObjetos2.util.HierarquiaUtils.*;

public class Cast extends ExpUnaria{

	private Tipo idClasseCast;

	public Cast(Expressao exp, Tipo idClasseCast) {
		super(exp, "(" + idClasseCast.getTipo() + ")");
		this.idClasseCast = idClasseCast;
	}

	public Valor avaliar(AmbienteExecucaoOO1 ambiente)
			throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException,
			ObjetoNaoDeclaradoException, ClasseNaoDeclaradaException {
		AmbienteExecucaoOO2 ambienteOO2 = (AmbienteExecucaoOO2) ambiente;
		Valor valor = this.getExp().avaliar(ambiente);
		Objeto objeto = ambienteOO2.getObjeto((ValorRef) valor);
		DefClasseOO2 expDefClasse = (DefClasseOO2) ambienteOO2.getDefClasse(objeto.getClasse());
		DefClasseOO2 castDefClasse = (DefClasseOO2) ambienteOO2.getDefClasse(this.idClasseCast.getTipo());
		if(!emMesmaHierarquia(expDefClasse, castDefClasse, ambienteOO2)){
			throw new ClassCastException("Tentativa de cast de " + expDefClasse.getIdClasse() + " para " + castDefClasse.getIdClasse());
		}
		return valor;
	}

	public Tipo getTipo(AmbienteCompilacaoOO1 ambiente)
			throws VariavelNaoDeclaradaException, ClasseNaoDeclaradaException {
		return this.idClasseCast;
	}

	@Override
	public boolean checaTipo(AmbienteCompilacaoOO1 ambiente)
			throws VariavelNaoDeclaradaException,
			VariavelNaoDeclaradaException, ClasseNaoDeclaradaException {
		boolean tipoOk = false;
		Tipo expType = this.getExp().getTipo(ambiente);
		if(super.checaTipo(ambiente) &&
				this.idClasseCast instanceof TipoClasse &&
				expType instanceof TipoClasse &&
				emMesmaHierarquia(expType, this.idClasseCast, (AmbienteCompilacaoOO2) ambiente)){
			tipoOk = true;
		}
		return tipoOk;
	}
}
